#include "vtsreadsample.hpp"
#include <cassert>
#include <new>
#include <vfwmsgs.h>
#include <climits>

namespace VTSRead
{

Sample* Sample::CreateInstance(IMemAllocator* alloc, const ALLOCATOR_PROPERTIES& p)
{
    assert(alloc);
    assert(p.cbAlign >= 1);
    assert(p.cbPrefix >= 0);

    Sample* const s = new (std::nothrow) Sample(alloc);
    
    if (s == 0)
        return 0;  //out of memory
    
    s->m_buflen = p.cbAlign - 1 + p.cbPrefix + p.cbBuffer;    
    s->m_buf = new (std::nothrow) BYTE[s->m_buflen];
    
    if (s->m_buf == 0)
    {
        delete s;
        return 0;
    }
    
    s->m_off = p.cbPrefix;
    
    if (intptr_t n = intptr_t(s->m_buf) % p.cbAlign)
        s->m_off += p.cbAlign - n;
        
    s->init();

    return s;
}


void Sample::DestroyInstance(Sample* p)
{
    if (p)
    {
        assert(p->m_refcount == 0);
        
        delete[] p->m_buf;
        delete p;
    }
}


Sample::Sample(IMemAllocator* p)
    : m_allocator(p),
      m_refcount(0)  //allocator will adjust 
{
}


Sample::~Sample()
{
}


void Sample::init()
{
    m_sync_point = false;
    m_actual_data_length = 0;
    m_preroll = false;
    m_discontinuity = false;
    m_start_time = LLONG_MAX;
    m_stop_time = LLONG_MAX;
    m_media_start_time = LLONG_MAX;
    m_media_stop_time = LLONG_MAX;
}
    

HRESULT Sample::QueryInterface(const IID& iid, void** ppv)
{
    IUnknown* p;
    
    if (iid == IID_IUnknown)
        p = this;
        
    else if (iid == IID_IMediaSample)
        p = this;
        
    //else if (iid == IID_IMediaSample2)
    //    p = this;
        
    else
    {
        if (ppv)
            *ppv = 0;
            
        return E_NOINTERFACE;
    }
    
    if (ppv == 0)
        return E_POINTER;
        
    p->AddRef();
    *ppv = p;
    
    return S_OK;
}


ULONG Sample::AddRef()
{
    return InterlockedIncrement(&m_refcount);
}


ULONG Sample::Release()
{
    assert(m_refcount > 0);

    const LONG result = InterlockedDecrement(&m_refcount);
    
    if (result > 0)
        return result;

    m_allocator->ReleaseBuffer(this);
    
    return 0;
}        
    

HRESULT Sample::GetPointer( 
    BYTE** pp)
{
    if (pp == 0)
        return E_POINTER;
        
    assert(m_buf);
    assert(m_off >= 0);
    
    BYTE*& p = *pp;

    p = m_buf + m_off;

    //TODO:
    //const ALLOCATOR_PROPERTIES& props = m_allocator->m_props;
    //assert(props.cbAlign >= 1);
    //assert(props.cbPrefix >= 0);
    //assert(intptr_t(p - props.cbPrefix) % props.cbAlign == 0);
    
    return S_OK;
}


long Sample::GetSize()
{
    assert(m_off <= m_buflen);

    const long result = m_buflen - m_off;

    return result;
}


HRESULT Sample::GetTime( 
    REFERENCE_TIME* pstart,
    REFERENCE_TIME* pstop)
{
    if (m_start_time == LLONG_MAX)
        return VFW_E_SAMPLE_TIME_NOT_SET;
        
    if (pstart)
        *pstart = m_start_time;
        
    if (m_stop_time == LLONG_MAX)
    {
        if (pstop)
            *pstop = m_start_time + 1; //?

        return VFW_S_NO_STOP_TIME;
    }
        
    if (pstop)
        *pstop = m_stop_time;
        
    return S_OK;
}


HRESULT Sample::SetTime( 
    REFERENCE_TIME* pstart,
    REFERENCE_TIME* pstop)
{
    if (pstart)
        m_start_time = *pstart;
    else
        m_start_time = LLONG_MAX;
        
    if (pstop)
        m_stop_time = *pstop;
    else
        m_stop_time = LLONG_MAX;
        
    return S_OK;
}


HRESULT Sample::IsSyncPoint()
{
    return m_sync_point ? S_OK : S_FALSE;
}


HRESULT Sample::SetSyncPoint(BOOL b)
{
    m_sync_point = bool(b != 0);
    return S_OK;
}


HRESULT Sample::IsPreroll()
{
    return m_preroll ? S_OK : S_FALSE;
}


HRESULT STDMETHODCALLTYPE Sample::SetPreroll(BOOL b)
{
    m_preroll = bool(b != 0);
    return S_OK;
}


long Sample::GetActualDataLength()
{
    return m_actual_data_length;
}


HRESULT Sample::SetActualDataLength(long len)
{
    m_actual_data_length = len;    
    return S_OK;
}


HRESULT Sample::GetMediaType( 
    AM_MEDIA_TYPE** pp)
{
    if (pp == 0)
        return E_POINTER;
        
    AM_MEDIA_TYPE*& p = *pp;
        
    p = 0;
    return S_FALSE;
}


HRESULT Sample::SetMediaType( 
    AM_MEDIA_TYPE* pmt)
{
    if (pmt == 0)
        return S_OK;
    
    if (pmt->pUnk)
        return E_INVALIDARG;
        
    if ((pmt->cbFormat != 0) && (pmt->pbFormat == 0))
        return E_INVALIDARG;
        
    return E_NOTIMPL;  //TODO
}


HRESULT Sample::IsDiscontinuity()
{
    return m_discontinuity ? S_OK : S_FALSE;
}


HRESULT Sample::SetDiscontinuity(BOOL b)
{
    m_discontinuity = bool(b != 0);
    return S_OK;
}


HRESULT Sample::GetMediaTime( 
    LONGLONG* pstart,
    LONGLONG* pstop)
{
    if (m_media_start_time == LLONG_MAX)
        return VFW_E_MEDIA_TIME_NOT_SET;

    if (pstart)
        *pstart = m_media_start_time;
        
    if (pstop)
    {
        if (m_media_stop_time != LLONG_MAX)
            *pstop = m_media_stop_time;
        else
            *pstop = m_media_start_time + 1; //?
    }
        
    return S_OK;
}


HRESULT Sample::SetMediaTime( 
    LONGLONG* pstart,
    LONGLONG* pstop)
{
    if (pstart)
        m_media_start_time = *pstart;
    else
        m_media_start_time = LLONG_MAX;
        
    if (pstop)
        m_media_stop_time = *pstop;
    else
        m_media_stop_time = LLONG_MAX;

    return S_OK;
}

}  //end namespace VTSRead
